using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Gko.Utils.Interfaces;

namespace Gko.Utils
{
	public abstract class DisposableBase : IDisposable2
	{
		private readonly List<IDisposable> disposables;
		private readonly List<IDisposable> unmanagedDisposables;
		private readonly object sync;
		private EventHandler disposedHandler;
		private EventHandler disposingHandler;
		private volatile bool isDisposed;
		private volatile bool isDisposing;

		protected DisposableBase()
		{
			this.sync = new object();
			this.disposables = new List<IDisposable>();
			this.unmanagedDisposables = new List<IDisposable>();
		}

		#region IDisposable2 Members

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Gets a value indicating whether this instance is disposed.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is disposed; otherwise, <c>false</c>.
		/// </value>
		public bool IsDisposed
		{
			get
			{
				return this.isDisposed;
			}
		}

		public bool IsDisposing
		{
			get
			{
				return this.isDisposing;
			}
		}

		/// <summary>
		/// Raised by the Dispose method immediately before disposing.
		/// </summary>
		/// <remarks>This event is raised only once.</remarks>
		public event EventHandler Disposing
		{
			add
			{
				lock (this.sync) {
					this.VerifyNotDisposing();
					this.disposingHandler += value;
				}
			}
			remove
			{
				lock (this.sync) {
					this.disposingHandler -= value;
				}
			}
		}

		/// <summary>
		/// Occurs when this object is disposed.
		/// </summary>
		public event EventHandler Disposed
		{
			add
			{
				lock (this.sync) {
					this.VerifyNotDisposed();
					this.disposedHandler += value;
				}
			}
			remove
			{
				lock (this.sync) {
					this.disposedHandler -= value;
				}
			}
		}

		#endregion

		/// <summary>
		/// Disposes object passed in when this instance is disposed
		/// </summary>
		/// <exception cref="ArgumentNullException"><c>disposable</c> is null.</exception>
		protected void AddDisposed(IDisposable disposable)
		{
			if (disposable == null) {
				throw new ArgumentNullException("disposable");
			}
			this.disposables.Add(disposable);
		}

		/// <exception cref="ArgumentNullException"><c>disposable</c> is null.</exception>
		protected void RemoveDisposed(IDisposable disposable)
		{
			if (disposable == null) {
				throw new ArgumentNullException("disposable");
			}
			this.disposables.Remove(disposable);
		}

		/// <summary>
		/// Disposes unmanaged wrapper passed in when this instance is disposed
		/// </summary>
		/// <exception cref="ArgumentNullException"><c>disposable</c> is null.</exception>
		protected void AddUnmanagedDisposed(IDisposable disposable)
		{
			if (disposable == null) {
				throw new ArgumentNullException("disposable");
			}
			this.unmanagedDisposables.Add(disposable);
		}

		/// <exception cref="ArgumentNullException"><c>disposable</c> is null.</exception>
		protected void RemoveUnmanagedDisposed(IDisposable disposable)
		{
			if (disposable == null) {
				throw new ArgumentNullException("disposable");
			}
			this.unmanagedDisposables.Remove(disposable);
		}

		~DisposableBase()
		{
			this.Dispose(false);
		}

		/// <summary>
		/// Verifies that the instance is not disposed.
		/// </summary>
		/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
		protected void VerifyNotDisposed()
		{
			if (this.isDisposed) {
				throw new ObjectDisposedException(this.GetType().Name);
			}
		}

		/// <summary>
		/// Verifies that the instance is not disposed.
		/// </summary>
		/// <exception cref="ObjectDisposedException">The instance is disposed.</exception>
		protected void VerifyNotDisposing()
		{
			if (this.isDisposing) {
				throw new ObjectDisposedException(this.GetType().Name);
			}
		}

		/// <summary>
		/// Called during the call to Dispose, immediately before Dispose(bool) is called.
		/// </summary>
		/// <remarks>The object is not yet marked as disposed, so VerifyNotDisposed will not throw an exception.</remarks>
		protected virtual void OnDisposing()
		{
		}

		private void Dispose(bool isDisposeManaged)
		{
			var bindable = this as IBindable;
			lock (this.sync) {
				if (this.isDisposing) {
					return;
				}
				this.isDisposing = true;
				if (bindable != null) {
					bindable.OnPropertyChanged("IsDisposing");
				}
			}

			this.OnDisposing();

			this.disposingHandler.Raise(this);

			if (isDisposeManaged) {
				this.OnDispose();
			}

			this.OnDisposeUnmanaged();

			this.isDisposed = true;
			if (bindable != null) {
				bindable.OnPropertyChanged("IsDisposed");
			}

			this.disposedHandler.Raise(this);
		}

		protected virtual void OnDisposeUnmanaged()
		{
		}

		protected virtual void OnDispose()
		{
			foreach (var disposable in this.disposables) {
				disposable.Dispose();
			}
		}
	}
}